package com.itheima.search.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.itheima.goods.feign.SkuFeign;
import com.itheima.goods.pojo.Sku;
import com.itheima.search.dao.SkuEsMapper;
import com.itheima.search.pojo.SkuInfo;
import com.itheima.search.service.SkuService;
import com.itheima.util.Result;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @program: itheima
 * @description
 * @author: Lindiedie
 * @create: 2020-06-01 22:17
 **/
@Service
public class SkuServiceImpl implements SkuService {

    @Autowired
    private SkuEsMapper skuEsMapper;
    @Autowired
    private SkuFeign skuFeign;
    @Autowired
    private ElasticsearchTemplate esTemplate;

    /***
     * 导入SKU数据
     */
    @Override
    public void importData() {
        // 远程调用商品微服务,获取状态为1的所有商品
        Result<List<Sku>> listResult = skuFeign.findByStatus("1");
        //将sku列表装换成Json串
        List<Sku> data = listResult.getData();
        String jsonString = JSONObject.toJSONString(data);
        //转换es储存的数据格式
        List<SkuInfo> skuInfoList = JSONObject.parseArray(jsonString, SkuInfo.class);
        //规格数据处理
        for (SkuInfo skuInfo : skuInfoList) {
            String spec = skuInfo.getSpec();
            Map<String, Object> map = JSONObject.parseObject(spec, Map.class);
            skuInfo.setSpecMap(map);
        }
        //将数据传入es
        skuEsMapper.saveAll(skuInfoList);
    }

    //es搜索实现
    //就是主方法的感觉,当我去访问的时候首先执行的就是这个方法
    @Override
    public Map<String, Object> search(Map<String, String> searchMap) {
        Map<String, Object> map = new HashMap<>();
        //条件构造器,查询条件
        NativeSearchQueryBuilder builder = getBaseQuery(searchMap);

        map = getSearch(builder);
        /*
        //执行查询,获取查询结果
        String category = searchMap.get("category");
        if (StringUtils.isEmpty(category)) {
            //获取类别列表
            List<String> categoryList = getCategory(builder);
            map.put("categoryList", categoryList);
        }


        String brand = searchMap.get("brand");
        if (StringUtils.isEmpty(brand)) {
            // 获取品牌列表
            List<String> list = searchBrandList(builder);
            map.put("brandList", list);
        }

        // 获取规格列表
        Map<String, Set<String>> searchSpec = searchSpec(builder);
        map.put("specList", searchSpec);
        */

        Map<String, Object> list = getList(builder, searchMap);
        map.putAll(list);
        return map;
    }

    /**
     * 条件封装
     * 用我自己的话来说就是自己整了一个创建NativeSearch的方法
     * 构建基本查询
     */
    private NativeSearchQueryBuilder getBaseQuery(Map<String, String> searchMap) {
        //查询构造器
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        //构建布尔查询
        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
        //获取搜索的参数
        String keywords = searchMap.get("keywords");
        //判空
        if (!StringUtils.isEmpty(keywords)) {
            //封装参数,构建条件
            queryBuilder.must(QueryBuilders.matchQuery("name", keywords));
//            builder.withQuery(QueryBuilders.matchQuery("name", keywords));
        }
        //品牌的名称查询
        String brand = searchMap.get("brand");
        if (!StringUtils.isEmpty(brand)) {
            // 品牌使用词条查询
            queryBuilder.must(QueryBuilders.termQuery("brandName", brand));
        }
        //类别的名称查询
        String category = searchMap.get("category");
        if (!StringUtils.isEmpty(category)) {
            // 类别使用词条查询
            queryBuilder.must(QueryBuilders.termQuery("categoryName", category));
        }

        //规格条件封装
        for (Map.Entry<String, String> entry : searchMap.entrySet()) {
            String key = entry.getKey();
            if (key.startsWith("spec_")) {
                String value = entry.getValue();
                queryBuilder.must(QueryBuilders.matchQuery("specMap." + key.substring(5) + ".keyword", value));
            }
        }
        //价格查询
        String price = searchMap.get("price");
        if (!StringUtils.isEmpty(price)) {
            //1.0-500元   2.3000以上
            price = price.replace("元", "").replace("以上", "");
            //1.0-500    2.3000
            String[] split = price.split("-");
            queryBuilder.must(QueryBuilders.rangeQuery("price").gt(split[0]));
            //如果有第二个值
            if (split.length > 1) {
                queryBuilder.must(QueryBuilders.rangeQuery("price").lte(split[1]));
            }
        }
        //分页查询
        Integer pageSize = 20;//每页显示多少条数据
        Integer pageNum = getPageNum(searchMap.get("pageNum"));//这是第几页
        //这里用的是Nativi的那个构造器
        builder.withPageable(PageRequest.of(pageNum - 1, pageSize));

        //排序
        String sortRule = searchMap.get("sortRule");
        String sortField = searchMap.get("sortField");
        if (!StringUtils.isEmpty(sortField)){
            builder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
        }

        //保存查询条件
        builder.withQuery(queryBuilder);
        return builder;
    }

    //分页类型转换
    private Integer getPageNum(String pageNum) {
        try {
            return Integer.parseInt(pageNum);
        } catch (Exception e) {
            return 1;
        }
    }

    /**
     * 执行查询
     */
    //最基础的查询结果,把查询到的封装成Map给主方法调用
    private Map getSearch(NativeSearchQueryBuilder builder) {
        Map<String, Object> map = new HashMap<>();
        /**
         * 设置高亮查询的相关条件
         */
        //1.对哪个域进行高亮查询
        HighlightBuilder.Field field = new HighlightBuilder
                .Field("name")//指定哪个域进行高亮
                .preTags("<span style='color:red'>")//这是前缀设置
                .postTags("</span>")//设置后缀
                .fragmentSize(100);//高亮数据的长度

        /**
         * 设置高亮查询
         */
        builder.withHighlightFields(field);

        //执行查询获取结果
        AggregatedPage<SkuInfo> skuInfos = esTemplate.queryForPage(builder.build(), SkuInfo.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<T> list = new ArrayList<>();
                //获取所有的查询结果
                SearchHits hits = response.getHits();
                //获取数据的迭代器
                Iterator<SearchHit> iterator = hits.iterator();
                //循环获取每条数据
                while (iterator.hasNext()) {
                    //这就是每一条数据
                    SearchHit next = iterator.next();
                    String sourceAsString = next.getSourceAsString();
                    SkuInfo skuInfo = JSONObject.parseObject(sourceAsString, SkuInfo.class);
                    //获取高亮的数据
                    HighlightField highlightField = next.getHighlightFields().get("name");
                    //这里循环获取所有的高亮数据
                    if (highlightField != null) {
                        //定义一个字符接收高亮数据
                        StringBuffer buffer = new StringBuffer();
                        //循环获取高亮数据
                        for (Text text : highlightField.getFragments()) {
                            buffer.append(text.toString());
                        }
                        skuInfo.setName(buffer.toString());
                    }
                    list.add((T) skuInfo);
                }
                /**
                 * @param list 返回查询结果的列表
                 * @param pageable 分页的信息
                 * @param response.getTotalShards() 总命中的数据量,就是说一共有多少条数据
                 */
                return new AggregatedPageImpl<>(list, pageable, response.getHits().getTotalHits());
            }
        });


        //获取商品列表
        List<SkuInfo> content = skuInfos.getContent();
        //符合条件的数据有多少条
        long totalElements = skuInfos.getTotalElements();
        //一共有多少页数据
        int totalPages = skuInfos.getTotalPages();
        //返回查询结果
        map.put("rows", content);
        map.put("totalElements", totalElements);
        map.put("pageNum", builder.build().getPageable().getPageNumber() + 1);
        map.put("pageSize", builder.build().getPageable().getPageSize());
        map.put("totalPages", totalPages);
        return map;
    }

    //类别查询
    private List<String> getCategory(NativeSearchQueryBuilder builder) {
        List<String> list = new ArrayList<>();
        //设置聚合的对象,要对哪个域进行去重
        /**
         * 1.取一个别名
         * 2.对哪个域进行去重聚合查询
         *
         */
        builder.addAggregation(AggregationBuilders.terms("category").field("categoryName"));
        //执行查询
        AggregatedPage<SkuInfo> skuInfos = esTemplate.queryForPage(builder.build(), SkuInfo.class);
        //获取全部的聚合结果
        Aggregations aggregations = skuInfos.getAggregations();
        //取出类别的聚合结果
        StringTerms aggregation = aggregations.get("category");
        for (StringTerms.Bucket bucket : aggregation.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();
            list.add(keyAsString);
        }
        return list;
    }


    /**
     * 聚合查询的条件封装
     *
     * @param builder
     * @return
     */
    private Map<String, Object> getList(NativeSearchQueryBuilder builder, Map<String, String> searchMap) {
        Map<String, Object> map = new HashMap<>();
        //品牌
        String brand = searchMap.get("brand");
        if (StringUtils.isEmpty(brand)) {
            builder.addAggregation(AggregationBuilders.terms("brand").field("brandName"));
        }


        //类别
        String category = searchMap.get("category");
        if (StringUtils.isEmpty(category)) {
            builder.addAggregation(AggregationBuilders.terms("category").field("categoryName"));
        }

        //规格
        builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword"));

        //执行查询
        AggregatedPage<SkuInfo> skuInfos = esTemplate.queryForPage(builder.build(), SkuInfo.class);

        //获取聚合结果
        Aggregations aggregations = skuInfos.getAggregations();
        //获取品牌的结果
        if (StringUtils.isEmpty(brand)) {
            List<String> brandlist = getList(aggregations, "brand");
            map.put("brandList", brandlist);
        }

        //获取类别的结果
        if (StringUtils.isEmpty(category)) {
            List<String> categorylist = getList(aggregations, "category");
            map.put("categoryList", categorylist);
        }

        //获取规格的结果
        List<String> skuSpec = getList(aggregations, "skuSpec");
        Map<String, Set<String>> specmap = getSpecList(skuSpec);
        map.put("specList", specmap);

        return map;
    }

    /**
     * 封装获取聚合结果方法(brand,category)
     *
     * @param aggregations
     * @param name
     * @return
     */
    private List<String> getList(Aggregations aggregations, String name) {
        List<String> list = new ArrayList<>();

        //获取分组结果
        StringTerms terms = aggregations.get(name);
        for (StringTerms.Bucket bucket : terms.getBuckets()) {
            list.add(bucket.getKeyAsString());
        }
        return list;
    }


    /**
     * 封装获取聚合结果方法(spec)
     *
     * @return
     */
    private Map<String, Set<String>> getSpecList(List<String> skuSpec) {
        Map<String, Set<String>> specmap = new HashMap<>();
        for (String s : skuSpec) {
            //将Json类型的字符串转换成Map类型
            Map<String, String> map = JSONObject.parseObject(s, Map.class);
            for (Map.Entry<String, String> entry : map.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                Set<String> set = specmap.get(key);
                if (set == null) {
                    set = new HashSet<String>();
                }
                set.add(value);
                specmap.put(key, set);
            }
        }
        return specmap;
    }


    /**
     * 查询品牌列表
     *
     * @param builder
     * @return
     */
    public List<String> searchBrandList(NativeSearchQueryBuilder builder) {
        //返回品牌名称集合
        List<String> list = new ArrayList<>();
        //设置聚合的对象,要对哪个域进行去重
        builder.addAggregation(AggregationBuilders.terms("brand").field("brandName"));
        //执行查询
        AggregatedPage<SkuInfo> skuInfos = esTemplate.queryForPage(builder.build(), SkuInfo.class);
        //获取聚合品牌结果
        Aggregations aggregations = skuInfos.getAggregations();
        //获取分组结果
        StringTerms terms = aggregations.get("brand");

        for (StringTerms.Bucket bucket : terms.getBuckets()) {
            list.add(bucket.getKeyAsString());
        }
        return list;
    }


    /**
     * 查询规格列表
     *
     * @param builder
     * @return
     */
    public Map<String, Set<String>> searchSpec(NativeSearchQueryBuilder builder) {

        //定义返回值类型
        Map<String, Set<String>> specmap = new HashMap<>();
        builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword"));
        AggregatedPage<SkuInfo> skuInfos = esTemplate.queryForPage(builder.build(), SkuInfo.class);
        Aggregations aggregations = skuInfos.getAggregations();
        StringTerms terms = aggregations.get("skuSpec");
        for (StringTerms.Bucket bucket : terms.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();
            //将Json类型的字符串转换成Map类型
            Map<String, String> map = JSONObject.parseObject(keyAsString, Map.class);
            for (Map.Entry<String, String> entry : map.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                Set<String> set = specmap.get(key);
                if (set == null) {
                    set = new HashSet<String>();
                }
                set.add(value);
                specmap.put(key, set);
            }
        }
        return specmap;
    }


}


